package org.emergent.lucidity.lucifer.client;

import org.emergent.lucidity.lucifer.client.bc.CipherParameters;
import org.emergent.lucidity.lucifer.client.bc.CAST5Engine;
import org.emergent.lucidity.lucifer.client.bc.BlockCipher;
import org.emergent.lucidity.lucifer.client.bc.CFBBlockCipher;
import org.emergent.lucidity.lucifer.client.bc.ParamsImpl;
import org.emergent.lucidity.lucifer.client.bc.AESEngine;
import org.emergent.lucidity.lucifer.client.bc.OpenPGPCFBBlockCipher;
import org.emergent.lucidity.lucifer.client.bc.DESedeEngine;
import org.emergent.lucidity.lucifer.client.bc.BlowfishEngine;
import org.emergent.lucidity.lucifer.client.bc.TwofishEngine;
import org.emergent.lucidity.lucifer.client.bc.SomewhatSecureRandom;
import org.emergent.lucidity.lucifer.client.jzlib.ZInputStream;
import org.emergent.lucidity.lucifer.client.jzlib.ZOutputStream;
import org.emergent.lucidity.lucifer.client.util.ByteArrayInputStream;
import org.emergent.lucidity.lucifer.client.util.ByteArrayOutputStream;

/**
 * User: patrick
 * Date: Apr 13, 2008
 * Time: 3:24:30 PM
 */
public class LuciferImpl
{
    private SomewhatSecureRandom rand = SomewhatSecureRandom.getInstance();

    public static byte[] string2bytes(String input)
    {
        byte[] bytes = new byte[input.length()];
        for (int ii = 0; ii < input.length(); ii++)
        {
            bytes[ii] = (byte)input.charAt( ii );
        }
        return bytes;
    }

    public static String bytes2string(byte[] input)
    {
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < input.length; i++)
        {
            buf.append( (char)(input[i] & 0xFF) );
        }
        return buf.toString();
    }

    public static void arraycopy( byte[] src, int srcPos, byte[] dest, int destPos, int length )
    {
        //noinspection ManualArrayCopy
        for (int ii = 0; ii < length; ii++)
        {
            dest[ii + destPos] = src[ii + srcPos];
        }
    }

    public static void arraycopy( int[] src, int srcPos, int[] dest, int destPos, int length )
    {
        //noinspection ManualArrayCopy
        for (int ii = 0; ii < length; ii++)
        {
            dest[ii + destPos] = src[ii + srcPos];
        }
    }

    public static LuciferImpl getLuciferInstance()
    {
        return new LuciferImpl();
    }

    public String randomString(int len ,boolean nozero)
    {
        byte[] data = new byte[len];
        rand.nextBytes( data );
        if (nozero)
        {
            for (int ii = 0; ii < data.length; ii++)
            {
                if (data[ii] == 0)
                    data[ii] = 1;
            }
        }
        String retval = bytes2string(data);
        return "\0" + retval;
    }

    public void updateEntropy(int inseed)
    {
        long seed = System.currentTimeMillis();
        if (inseed != 0)
        {
            seed <<= 32;
            seed += inseed;
        }
        rand.setSeed( seed );
    }

    public String decryptString(boolean pgpcfb,byte algo,String ciphertext,String keystr,String ivstr)
    {
        return cryptString( false, pgpcfb, algo, ciphertext, keystr, ivstr );
    }

    public String encryptString(boolean pgpcfb,byte algo,String ciphertext,String keystr,String ivstr)
    {
        return cryptString( true, pgpcfb, algo, ciphertext, keystr, ivstr );
    }

    private static String cryptString(boolean encrypt,boolean pgpcfb,byte algo,String ciphertext,String keystr,String ivstr)
    {
        try
        {
            if (ivstr != null)
            {
                if ("undefined".equals(ivstr))
                {
                    ivstr = null;
                }
            }
            BlockCipher engine;
            switch (algo) {
                case 2:
                    engine = new DESedeEngine();
                    break;
                case 3:
                    engine = new CAST5Engine();
                    break;
                case 4:
                    engine = new BlowfishEngine();
                    break;
                case 7:
                case 8:
                case 9:
                    engine = new AESEngine();
                    break;
                case 10:
                    engine = new TwofishEngine();
                    break;
                default:
                    throw new RuntimeException( "bad algo: " + algo);
            }
            BlockCipher c;
            int blocksize = engine.getBlockSize();
            if (pgpcfb)
            {
                c = new OpenPGPCFBBlockCipher(engine);
            }
            else
            {
                c = new CFBBlockCipher(engine, blocksize * 8);
            }
            byte[] ivBytes;
            if (ivstr == null)
            {
                ivBytes = new byte[blocksize];
            }
            else
            {
                ivBytes = string2bytes( ivstr );
            }
            byte[] keyBytes = string2bytes( keystr );
            byte[] encData = string2bytes( ciphertext );
            CipherParameters params = new ParamsImpl( keyBytes, ivBytes );
            c.init(encrypt,params);

            byte[] data = null;

            int blockcount = encData.length / blocksize;
            if (encData.length % blocksize != 0)
            {
                blockcount++;
                data = new byte[blockcount * blocksize];
                arraycopy( encData, 0, data, 0, encData.length );
                encData = data;
            }

            data = new byte[blockcount * blocksize];

            for (int i = 0; i < blockcount; i++)
            {
                int offset = i * blocksize;
                c.processBlock( encData, offset, data, offset );
            }
            String retval = bytes2string(data);
            return "\0" + retval.substring( 0, encData.length );
        }
        catch (Exception e)
        {
            return "\1" + e.toString();
        }
    }

    public String hashString( byte algorithm, String input )
    {
        try
        {
            throw new RuntimeException( "hi there!" );
        }
        catch (Throwable e)
        {
            return "\1" + e.toString();
        }
    }

    public String inflateString( byte algorithm, String input )
    {
        if (algorithm == 0)
            return "\0" + input;
        try
        {
            StringBuffer buf = new StringBuffer();
            buf.append('\0');
            byte bytes[] = new byte[input.length()];
            for (int ii = 0; ii < input.length(); ii++)
            {
                bytes[ii] = (byte)(input.charAt( ii ) & 0xFF);
            }
//            ZStream is = new ZStream( bytes, algorithm == 1 );
            ZInputStream is = new ZInputStream( new ByteArrayInputStream( bytes ), algorithm == 1 );
            int thebyte;
            while ((thebyte = is.read()) != -1)
            {
                buf.append( (char)thebyte );
            }
            return buf.toString();
        }
        catch (Throwable e)
        {
            return "\1" + e.toString();
        }
    }

    public String deflateString( byte algorithm, String input )
    {
        if (algorithm == 0)
            return "\0" + input;
        try
        {
            StringBuffer buf = new StringBuffer();
            buf.append('\0');
            byte bytes[] = new byte[input.length()];
            for (int ii = 0; ii < input.length(); ii++)
            {
                bytes[ii] = (byte)(input.charAt( ii ) & 0xFF);

            }
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ZOutputStream is = new ZOutputStream( baos, -1, algorithm == 1 );
            is.write(bytes);
            is.flush();
            is.close();
            byte newbytes[] = baos.toByteArray();
            for (int ii = 0; ii < newbytes.length; ii++)
            {
                buf.append( (char)newbytes[ii] );
            }
            return buf.toString();
        }
        catch (Throwable e)
        {
            return "\1" + e.toString();
        }
    }
}
